/*************************************************************************
	> File Name: sclient.cpp
	> Author: xuwenlong
	> Mail: myxuan475@126.com 
	> Created Time: 2018年10月08日 星期一 15时30分34秒
 ************************************************************************/
#include "sclient.h"
#include <iostream>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <string.h>
#include <event.h>
#include <logger.h>
#include <event2/thread.h>
#include <event2/dns.h>
#include <error.h>
#include <sstream>

Sclient::Sclient(ProtocolHandler *pHandler, bool heart):
    m_EvBase(NULL),
    m_pProtocolHandler(pHandler),
    m_lock(true),
    m_bHeart(heart),
    m_iFd(-1),
    m_pEvent(NULL),
    m_bufferLock(true) {

    addReference();
    if (pHandler)
        pHandler->AppendTransport(this);

    /*
     *  初始化
     */
    evthread_use_pthreads();
    m_EvBase = event_base_new();
    evthread_make_base_notifiable(m_EvBase);
}

Sclient::~Sclient()
{
    stopClient();
    ExitLoop();
    delete m_pProtocolHandler;
}

void Sclient::Init()
{
    m_pProtocolHandler->Init();
}

void Sclient::RunLoop(const char *host,const char *ipaddr,int port)
{
    if (m_pProtocolHandler)
        m_pProtocolHandler->ClearRecvData();

    if (!startClient(host,ipaddr,port)) {
        LOG_ERR("connect to %s:%d failed",ipaddr,port);
        return;
    }
    AutoLock aulock(m_lock);
    if (m_EvBase) {
        AutoUnlock unlock(m_lock);
        event_base_dispatch(m_EvBase);
    }
    stopClient();
}

bool Sclient::startClient(
        const char *hostname,
        const char *ipAddr,
        int port)
{
    AutoLock aulock(m_lock);

    stringstream ss;
    ss << hostname<<":"<<port;
    m_url = ss.str();

    m_iFd = get_addrinfo_for_host(hostname,port);

    if (m_iFd < 0) {
        m_iFd = get_hostname_for_host(hostname,port);
        if (m_iFd < 0) {
            m_iFd = get_addrinfo_for_host(ipAddr,port);
        }
        if (m_iFd < 0)
            return false;
    }

//    evutil_make_socket_nonblocking(m_iFd);

    /* 使socket 可重用 */
    evutil_make_listen_socket_reuseable(
                bufferevent_getfd(m_bev));
    evutil_make_listen_socket_reuseable_port(
                bufferevent_getfd(m_bev));
    bufferevent_setcb(
                m_bev,
                Sclient::server_msg,
                NULL,
                Sclient::server_event,this);
    bufferevent_enable(m_bev,EV_READ|EV_WRITE|EV_PERSIST);

    return true;
}

void Sclient::stopClient()
{
//    AutoLock aulock(m_lock);
    stopSession();
    OnStatus(false);
    if (m_EvBase) {
        //        struct timeval delay = { 5, 0 };
//        event_base_loopexit(m_EvBase,NULL);
    }
}

evutil_socket_t
Sclient::get_addrinfo_for_host(const char *hostname, uint16_t port)
{
    char port_buf[6] = {0};
    struct evutil_addrinfo hints;
    struct evutil_addrinfo *answer = NULL;
    int err;
    evutil_socket_t sock;

    /* Convert the port to decimal. */
    evutil_snprintf(port_buf, sizeof(port_buf), "%d", (int)port);

    /* Build the hints to tell getaddrinfo how to act. */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC; /* v4 or v6 is fine. */
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP; /* We want a TCP socket */
    /* Only return addresses we can use. */
    hints.ai_flags = EVUTIL_AI_ADDRCONFIG;

    /* Look up the hostname. */
    err = evutil_getaddrinfo(hostname, port_buf, &hints, &answer);
    if (err != 0) {

          LOG_ERR("Error while resolving '%s': %s",
                  hostname, evutil_gai_strerror(err));
          if (err != 0)
            return -1;
    }

    /* If there was no error, we should have at least one answer. */
    if (!answer)
        return -1;
    /* Just use the first answer. */
    sock = socket(answer->ai_family,
                  answer->ai_socktype,
                  answer->ai_protocol);

    if (sock < 0) {
        evutil_freeaddrinfo(answer);
        LOG_ERR("create socket failed:%s",strerror(errno));
        return -1;
    }

    m_bev = bufferevent_socket_new(
                m_EvBase,sock,
                BEV_OPT_CLOSE_ON_FREE|BEV_OPT_THREADSAFE);

    if (m_bev == NULL) {
        LOG_ERR("bind socket libevent failed:%s",strerror(errno));
        freeaddrinfo(answer);
        EVUTIL_CLOSESOCKET(sock);

        return -1;
    }

    if (-1 == bufferevent_socket_connect(m_bev, answer->ai_addr, answer->ai_addrlen)) {
        /* Note that we're doing a blocking connect in this function.
         * If this were nonblocking, we'd need to treat some errors
         * (like EINTR and EAGAIN) specially. */
        freeaddrinfo(answer);
        EVUTIL_CLOSESOCKET(sock);
        bufferevent_free(m_bev);
        m_bev = NULL;
        return -1;
    }
    freeaddrinfo(answer);

    return sock;
}

evutil_socket_t Sclient::get_hostname_for_host(const char *hostname, uint16_t port)
{
    evutil_socket_t sock;
    sockaddr_in addr;
    struct hostent *host = gethostbyname(hostname); //<netdb.h>
    if (host == NULL)
    {
        LOG_ERR("hostname get failed! %s",strerror(errno));
        return -1;
    }
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    memcpy(&(addr.sin_addr.s_addr), host->h_addr_list[0], 4);

    sock = socket(AF_INET, SOCK_STREAM, 0);

    if (sock < 0) {
        LOG_ERR("create socket failed:%s",strerror(errno));
        return -1;
    }

    m_bev = bufferevent_socket_new(
                m_EvBase,sock,
                BEV_OPT_CLOSE_ON_FREE|BEV_OPT_THREADSAFE);

    if (m_bev == NULL) {
        LOG_ERR("bind socket libevent failed:%s",strerror(errno));
        EVUTIL_CLOSESOCKET(sock);

        return -1;
    }
    if (-1 == bufferevent_socket_connect(m_bev,(sockaddr*)&addr, sizeof(addr))) {
        /* Note that we're doing a blocking connect in this function.
         * If this were nonblocking, we'd need to treat some errors
         * (like EINTR and EAGAIN) specially. */
        EVUTIL_CLOSESOCKET(sock);
        bufferevent_free(m_bev);
        m_bev = NULL;
        return -1;
    }
    return sock;
}

void Sclient::OnReceiveData(void *data, S32 bytes)
{
    m_pProtocolHandler->OnReceiveData(data,bytes);
//    m_pProtocolHandler->RpcParse();
}

BOOL Sclient::SendData(const char *data, S32 bytes)
{
    LOG_TRACE();
    AutoLock autolock(m_bufferLock);
    if (!m_bev)
        return false;
    LOG_DEBUG("send:%d bytes",bytes);
    return (0 == bufferevent_write(m_bev,data,bytes));
}

void Sclient::ChangeStatus(bool connect)
{
    if (!connect)
        stopClient();
}

void Sclient::TimeoutEvent()
{
//    return;
    if (-1 == m_pProtocolHandler->SendJsonRpc(
                this,"heartBeat",Json::Value(),30)) {
        stopClient();
    }
}

BOOL Sclient::OnReceiveJson(
        S32 index,
        DAS_RESULT status,
        const string &method,
        const Json::Value &result)
{
    if (method == "heartBeat" ) {
        if (status == DAS_SUCCESS &&
                result.isBool() && result.asBool() == true) {
            if (m_pProtocolHandler->GetIndex() != -1)
                SetMsgStatus(DAS_MSG_STATUS,TRUE,m_pProtocolHandler->GetIndex());
        }
        else {
//            stopClient();
        }

    }
    return TRUE;
}

void Sclient::server_msg(bufferevent *bev, void *arg)
{
    Sclient *pWork = (Sclient *)arg;
    struct evbuffer* input = bufferevent_get_input(bev);
    size_t length = evbuffer_get_length(input);
    char *buf = (char*)malloc(length+1);
    evbuffer_remove(input, buf, length);
    buf[length] = 0;
    LOG_DEBUG("recv:%d bytes",length);
    pWork->OnReceiveData(buf,length);
    free(buf);
}

void Sclient::server_event(bufferevent *bev, short ev, void *arg)
{
    Sclient *client = static_cast<Sclient*>(arg);
    short err = 0;

    err = (BEV_EVENT_ERROR);
    while (ev & err) {

        LOG_ERR("socket(%s) error,%d,%s.",
                client->session_url().c_str(),
                (ev&err),
                evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));

        client->stopSession();
        client->OnStatus(false);
        break;
    }

    err = BEV_EVENT_CONNECTED;
    while (ev & err) {
        LOG_INFO("Client(%s) has been successfully connected.",
                 client->session_url().c_str());
        client->OnStatus(true);
        client->setTimeoutEvent();
        break;
    }

    err = BEV_EVENT_TIMEOUT;
    while (ev & err) {
        LOG_ERR("%s timeout,%d,%s.",
                client->session_url().c_str(),
                (ev&err),
                evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
        break;
    }

    err = BEV_EVENT_EOF|BEV_EVENT_READING|BEV_EVENT_WRITING;
    while (ev & err) {

        LOG_INFO("Connection(%s) closed,ev = %d,%s",
                 client->session_url().c_str(),
                 ev&err,
                 evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
        client->stopSession();
        client->OnStatus(false);
        break;
    }
}

void Sclient::timeout_event(evutil_socket_t fd, short ev, void *arg)
{
//    server_event(NULL,EV_TIMEOUT,arg);
    Sclient *client = static_cast<Sclient*>(arg);
    client->TimeoutEvent();
}

void Sclient::setTimeoutEvent()
{
    AutoLock aulock(m_lock);
    m_pEvent = event_new(
                m_EvBase,
                m_iFd,
                EV_TIMEOUT|EV_PERSIST,
                timeout_event,this);
    struct timeval timeout = {m_bHeart?15:240, 0};
    event_add(m_pEvent, &timeout);
}

void Sclient::stopSession()
{
    AutoLock aulock(m_lock);
    if (m_pEvent) {
        event_del(m_pEvent);
        event_free(m_pEvent);
        m_pEvent = NULL;
    }

    AutoLock bevlock(m_bufferLock);
    if (m_bev) {
        LOG_DEBUG("close forcely");
//        evutil_closesocket(bufferevent_getfd(m_bev));
        int fd = bufferevent_getfd(m_bev);
        bufferevent_free(m_bev);
//        bufferevent_disable(m_bev, EV_READ|EV_WRITE);
//        shutdown(bufferevent_getfd(m_bev), SHUT_WR);
//        evutil_closesocket(bufferevent_getfd(m_bev));

        evutil_closesocket(fd);
//        shutdown(fd, SHUT_RDWR);
        m_bev = NULL;
    }
}

string Sclient::session_url()
{
    return m_url;
}

void Sclient::ExitLoop()
{
    stopClient();
    AutoLock aulock(m_lock);
    if (m_EvBase) {
        //        struct timeval delay = { 5, 0 };
        event_base_loopexit(m_EvBase,NULL);
        event_base_free(m_EvBase);
        m_EvBase = NULL;
    }

    if (m_bev) {
        bufferevent_disable(m_bev, EV_READ|EV_WRITE);
        bufferevent_free(m_bev);
        shutdown(m_iFd, SHUT_RDWR);

        m_bev = NULL;
    }
//    if (m_iFd > 0)
    //        shutdown(m_iFd, SHUT_RDWR);
}
